package com.wyzt.search.service;

import com.fasterxml.jackson.core.type.TypeReference;
import com.wyzt.common.enums.ExceptionEnum;
import com.wyzt.common.exception.WyztException;
import com.wyzt.common.utils.JsonUtils;
import com.wyzt.common.utils.NumberUtils;
import com.wyzt.common.vo.PageResult;
import com.wyzt.item.bo.SpuBo;
import com.wyzt.item.pojo.*;
import com.wyzt.search.client.BrandClient;
import com.wyzt.search.client.CategoryClient;
import com.wyzt.search.client.GoodsClient;
import com.wyzt.search.client.SpecificationClient;
import com.wyzt.search.pojo.Goods;
import com.wyzt.search.pojo.SearchRequest;
import com.wyzt.search.pojo.SearchResult;
import com.wyzt.search.repository.GoodsRepository;
import lombok.extern.slf4j.Slf4j;
import net.bytebuddy.asm.Advice;
import org.apache.commons.lang3.StringUtils;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.index.query.MatchQueryBuilder;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.aggregations.Aggregation;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.Aggregations;
import org.elasticsearch.search.aggregations.bucket.histogram.InternalHistogram;
import org.elasticsearch.search.aggregations.bucket.terms.LongTerms;
import org.elasticsearch.search.aggregations.bucket.terms.StringTerms;
import org.elasticsearch.search.aggregations.metrics.stats.InternalStats;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.elasticsearch.core.ElasticsearchTemplate;
import org.springframework.data.elasticsearch.core.ResultsExtractor;
import org.springframework.data.elasticsearch.core.aggregation.AggregatedPage;
import org.springframework.data.elasticsearch.core.query.FetchSourceFilter;
import org.springframework.data.elasticsearch.core.query.NativeSearchQuery;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import java.util.*;
import java.util.stream.Collector;
import java.util.stream.Collectors;
import java.util.stream.Stream;

@Service
@Slf4j
public class SearchService {

    @Autowired
    private CategoryClient categoryClient;
    @Autowired
    private BrandClient brandClient;

    @Autowired
    private GoodsClient goodsClient;
    @Autowired
    private SpecificationClient specificationClient;

    @Autowired
    private GoodsRepository goodsRepository;

    @Autowired
    private ElasticsearchTemplate template;

    /**
     * 构建goods，用于保存至elasticSearch
     *
     * @param spu
     * @return
     */
    public Goods buildGoods(SpuBo spu) {
        //查询分类
        List<Category> categories = categoryClient.queryCategoryListByIds(
                Arrays.asList(spu.getCid1(), spu.getCid2(), spu.getCid3())
        );
        List<String> names = categories.stream().map(Category::getName).collect(Collectors.toList());
        //查询品牌
        Brand brand = brandClient.queryBrandById(spu.getBrandId());
        if (brand == null) {
            throw new WyztException(ExceptionEnum.GOODS_NOT_FOUND);
        }
        //搜索字段
        String all = spu.getTitle() + StringUtils.join(names, " ") + brand.getName();


        //查询sku价格
        List<Sku> skuList = goodsClient.querySkuListById(spu.getId());
        if (CollectionUtils.isEmpty(skuList)) {
            throw new WyztException(ExceptionEnum.GOODS_SKU_NOT_FOUND);
        }
        //对sku进行处理
        List<Map<String, Object>> skus = new ArrayList<>();
        //处理价格
        List<Long> priceList = new ArrayList<>();
        skuList.forEach(sku -> {
            Map<String, Object> map = new HashMap<>();
            map.put("id", sku.getId());
            map.put("title", sku.getTitle());
            map.put("price", sku.getPrice());
            map.put("image", StringUtils.substringBefore(sku.getImages(), ","));
            skus.add(map);
            priceList.add(sku.getPrice());
        });

        //查询规格参数
        SpuDetail spuDetail = goodsClient.querySpuDetailById(spu.getId());
        //将可搜索的属性导入
        String specifications = spuDetail.getSpecifications();
        //将可搜索的字符串转为对象
        List<Map<String, Object>> maps =
                JsonUtils.nativeRead
                        (specifications, new TypeReference<List<Map<String, Object>>>() {
                        });
        //新建map用于存储可搜索属性
        //规格参数
        HashMap<String, Object> specMaps = new HashMap<>();
        for (Map<String, Object> map : maps) {
            List<Map<String, Object>> paramList = (List<Map<String, Object>>) map.get("params");
            for (Map<String, Object> paramMap : paramList) {
                Boolean searchable = (Boolean) paramMap.get("searchable");
                if (searchable) {
                    if (paramMap.get("v") != null) {
                        specMaps.put((String) paramMap.get("k"), paramMap.get("v"));
                    } else if (paramMap.get("options") != null) {
                        specMaps.put((String) paramMap.get("k"), paramMap.get("options"));
                    }
                }
            }
        }

        //构建goods对象
        Goods goods = new Goods();
        goods.setBrandId(spu.getBrandId());
        goods.setCid1(spu.getCid1());
        goods.setCid2(spu.getCid2());
        goods.setCid3(spu.getCid3());
        goods.setCreateTime(spu.getCreateTime());
        goods.setId(spu.getId());
        goods.setSubTitle(spu.getSubTitle());

        goods.setAll(all);//搜索字段，包含标题、分类、品牌、规格等
        goods.setPrice(priceList);//  所有sku的价格集合
        goods.setSkus(JsonUtils.serialize(skus));// 所有sku的集合的json格式
        goods.setSpecs(specMaps);// 所有的可搜索的规格参数

        return goods;
    }

    public PageResult<Goods> page(SearchRequest request) {
        int page = request.getPage() - 1;
        int size = request.getSize();
        String key = request.getKey();
        if (StringUtils.isBlank(key)) {
            //判断是否有搜索条件，如果没有，直接返回null。不允许搜索所有商品
            return null;
        }

        //创建查询构建器
        NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
        //0. 结果过滤
        queryBuilder.withSourceFilter(new FetchSourceFilter(
                new String[]{"id", "subTitle", "skus"},
                null
        ));
        //1. 分页
        queryBuilder.withPageable(PageRequest.of(page, size));
        //2. 过滤 查询条件
        QueryBuilder basicQuery = QueryBuilders.matchQuery("all", key);
        queryBuilder.withQuery(basicQuery);
        //3. 聚合分类和品牌
        //3.1 聚合分类
        String categoryAggName = "categoryAgg";
        queryBuilder.addAggregation(AggregationBuilders.terms(categoryAggName).field("cid3"));
        //3.2 聚合品牌
        String brandAggName = "brandAgg";
        queryBuilder.addAggregation(AggregationBuilders.terms(brandAggName).field("brandId"));
        //4. 查询
//        Page<Goods> result = goodsRepository.search(queryBuilder.build());
        AggregatedPage<Goods> result = template.queryForPage(queryBuilder.build(), Goods.class);

        //5. 解析结果
        //5.1 解析分页结果
        long total = result.getTotalElements();
        long totalPage = result.getTotalPages();
        List<Goods> goodsList = result.getContent();
        //5.2 解析聚合结果
        Aggregations aggregations = result.getAggregations();
        List<Category> categories = parseCategoryAgg(aggregations.get(categoryAggName));
        List<Brand> brands = parseBrandAgg(aggregations.get(brandAggName));

        //6. 规格参数聚合
        List<Map<String, Object>> specs = null;
        // TODO: 2018/12/5 暂不进行specs的处理，可能是数据有问题
//        if (categories != null && categories.size() == 1) {
//            //商品分类存在并且数量为1，可以聚合规格参数
//            specs = buildSpecificationAgg(categories.get(0).getId(), basicQuery);
//        }
        return new SearchResult(total, totalPage, goodsList, categories, brands, specs);
    }

    private List<Map<String, Object>> buildSpecificationAgg(Long cid, QueryBuilder basicQuery) {
        List<Map<String, Object>> specs = new ArrayList<>();
        //1、查询需要聚合的规格参数
        String specification = specificationClient.querySpecificationByCid(cid);
        try {
            specs = JsonUtils.nativeRead(specification, new TypeReference<List<Map<String, Object>>>() {
            });
        } catch (Exception e) {
            log.error("解析规格参数json出错，json={}", specification, e);
            return null;
        }

        //2、聚合，过滤出可以搜索的哪些规格参数的名称，分成数值类型、字符串类型
        Set<String> strSpec = new HashSet<>();
        Map<String, String> numericalUnits = new HashMap<>();
        //解析规格
        for (Map<String, Object> spec : specs) {
            List<Map<String, Object>> params = (List<Map<String, Object>>) spec.get("params");
            for (Map<String, Object> param : params) {
                Boolean searchable = (Boolean) param.get("searchable");
                if (searchable) {
                    //判断是否是数值类型
                    if (param.containsKey("numerical") && (Boolean) param.get("numerical")) {
                        numericalUnits.put(param.get("k").toString(), param.get("unit").toString());
                    } else {
                        strSpec.add(param.get("k").toString());
                    }
                }
            }
        }

        //3、获取结果
        Map<String, Double> numericalInterval = getNumericalInterval(cid, numericalUnits.keySet());
        //4、解析结果
        //5、对字符串类型的参数进行聚合
        return aggForSpec(strSpec, numericalInterval, numericalUnits, basicQuery);
    }

    //根据规格参数，聚合得出过滤条件
    private List<Map<String, Object>> aggForSpec(Set<String> strSpec,
                                                 Map<String, Double> numericalInterval,
                                                 Map<String, String> numericalUnits,
                                                 QueryBuilder query) {

        List<Map<String, Object>> specs = new ArrayList<>();
        //准备查询条件
        NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
        queryBuilder.withQuery(query);
        //聚合数值类型
        for (Map.Entry<String, Double> entry : numericalInterval.entrySet()) {
            queryBuilder.addAggregation(
                    AggregationBuilders
                            .histogram(entry.getKey())
                            .field("`" + entry.getKey())
                            .interval(entry.getValue())
                            .minDocCount(1)
            );
        }
        //聚合字符串
        for (String key : strSpec) {
            queryBuilder.addAggregation(AggregationBuilders.terms(key).field("specs." + key + ".keyword"));
        }

        //解析聚合结果
        Map<String, Aggregation> aggs = template.query(queryBuilder.build(), SearchResponse::getAggregations).asMap();

        //解析数值类型
        for (Map.Entry<String, Double> entry : numericalInterval.entrySet()) {
            Map<String, Object> spec = new HashMap<>();
            String key = entry.getKey();
            spec.put("k", key);
            spec.put("unit", numericalUnits.get(key));
            //获取聚合结果
            InternalHistogram histogram = (InternalHistogram) aggs.get(key);
            spec.put("options", histogram.getBuckets().stream().map(bucket -> {
                Double begin = (Double) bucket.getKey();
                Double end = begin + numericalInterval.get(key);
                //对begin和end进行取整
                if (NumberUtils.isInt(begin) && NumberUtils.isInt(end)) {
                    //确实是整数，需要取整
                    return begin.intValue() + "-" + end.intValue();
                } else {
                    //小数、取2位小数
                    begin = NumberUtils.scale(begin, 2);
                    end = NumberUtils.scale(end, 2);
                    return begin + "-" + end;
                }
            }));
            specs.add(spec);
        }

        //解析字符串类型
        strSpec.forEach(key -> {
            Map<String, Object> spec = new HashMap<>();
            spec.put("k", key);
            StringTerms terms = (StringTerms) aggs.get(key);
            spec.put("options", terms.getBuckets().stream().map(bucket -> bucket.getKeyAsString()));
            specs.add(spec);
        });
        return specs;
    }

    //聚合得到interval
    private Map<String, Double> getNumericalInterval(Long cid, Set<String> keySet) {
        Map<String, Double> numericalSpecs = new HashMap<>();
        //准备查询条件
        NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
        //不查询任何数据
        queryBuilder
                .withQuery(QueryBuilders.termQuery("cid3", cid.toString()))
                .withSourceFilter(new FetchSourceFilter(new String[]{""}, null))
                .withPageable(PageRequest.of(0, 1));
        //添加stats类型的聚合
        // TODO: 2018/12/5 非数值类型无法进行聚合
        for (String key : keySet) {
            queryBuilder.addAggregation(AggregationBuilders.stats(key).field("specs." + key));
        }
        Map<String, Aggregation> aggs = template.query(
                queryBuilder.build(),
                response -> response.getAggregations().asMap());
        for (String key : keySet) {
            InternalStats stats = (InternalStats) aggs.get(key);
            double interval = getInterval(stats.getMin(), stats.getMax(), stats.getSum());
            numericalSpecs.put(key, interval);
        }
        return numericalSpecs;

    }

    //根据最大值、最小值、sum计算interval
    private double getInterval(double min, double max, Double sum) {
        double interval = (max - min) / 6.0d;
        //判断是否小数
        if (sum.intValue() == sum) {
            //不是小数，要取整十、整百这种
            //根据interval的整数长度来判断位数
            int length = StringUtils.substringBefore(String.valueOf(interval), ".").length();
            double factor = Math.pow(10.0, length - 1);
            return Math.round(interval / factor) * factor;
        } else {
            //是小数，我们只保留一位小数
            return NumberUtils.scale(interval, 1);
        }
    }

    private List<Brand> parseBrandAgg(LongTerms terms) {
        try {
            List<Long> ids = terms.getBuckets()
                    .stream()
                    .map(b -> b.getKeyAsNumber().longValue())
                    .collect(Collectors.toList());
            List<Brand> brands = brandClient.queryBrandByIds(ids);
            return brands;
        } catch (Exception e) {
            log.error("[搜索服务] 查询品牌异常", e);
            return null;
        }

    }

    private List<Category> parseCategoryAgg(LongTerms terms) {
        try {
            List<Long> ids = terms.getBuckets()
                    .stream()
                    .map(b -> b.getKeyAsNumber().longValue())
                    .collect(Collectors.toList());
            List<Category> categories = categoryClient.queryCategoryListByIds(ids);
            return categories;
        } catch (Exception e) {
            log.error("[搜索服务] 查询分类异常", e);
            return null;
        }
    }

    public void createOrUpdateIndex(Long spuId) {
        //查询spu
        SpuBo spuBo = goodsClient.querySpuBoById(spuId);
        //构建goods
        Goods goods = buildGoods(spuBo);
        //存入索引库
        goodsRepository.save(goods);
    }

    public void deleteIndex(Long spuId) {

        goodsRepository.deleteById(spuId);
    }
}
